// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ValueExpiry.cs" company="XamlNinja">
//   2011 Richard Griffin and Ollie Riches
// </copyright>
// <summary>
// </summary>
// <credits>
// StuartHarris http://red-badger.com/Blog/post/Watching-Silverlight-Dependency-Properties-with-Reactive-Extensions.aspx#comment
// </credits>
// --------------------------------------------------------------------------------------------------------------------

namespace WP7Contrib.View.Controls.Behaviors
{
    using System;
    using System.Reactive;
    using System.Reactive.Disposables;
    using System.Reactive.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Input;
    using System.Windows.Interactivity;

    using WP7Contrib.View.Controls.BindingListener;

    public class ButtonCommand : Behavior<Button>
    {
        public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.Register(
            "CommandBinding", typeof(Binding), typeof(ButtonCommand), new PropertyMetadata(CommandBindingChanged));

        public static readonly DependencyProperty CommandParameterBindingProperty =
            DependencyProperty.Register(
                "CommandParameterBinding", 
                typeof(Binding), 
                typeof(ButtonCommand), 
                new PropertyMetadata(CommandParameterBindingChanged));

        private ICommand command;

        private object commandParameter;

        private CompositeDisposable subscriptions;

        public ICommand Command
        {
            get
            {
                return this.command;
            }

            set
            {
                if (value != this.command)
                {
                    this.command = value;
                    this.CommandChanged();
                }
            }
        }

        public Binding CommandBinding
        {
            get
            {
                return (Binding)this.GetValue(CommandBindingProperty);
            }

            set
            {
                this.SetValue(CommandBindingProperty, value);
            }
        }

        public object CommandParameter
        {
            get
            {
                return this.commandParameter;
            }

            set
            {
                if (value != this.commandParameter)
                {
                    this.commandParameter = value;
                    this.CommandParameterChanged();
                }
            }
        }

        public Binding CommandParameterBinding
        {
            get
            {
                return (Binding)this.GetValue(CommandParameterBindingProperty);
            }

            set
            {
                this.SetValue(CommandParameterBindingProperty, value);
            }
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            Observable.FromEventPattern(
                (EventHandler<RoutedEventArgs> h) => new RoutedEventHandler(h), 
                handler => this.AssociatedObject.Loaded += handler, 
                handler => this.AssociatedObject.Loaded -= handler).Subscribe(@event => this.Bind());
            Observable.FromEventPattern(
                (EventHandler<RoutedEventArgs> h) => new RoutedEventHandler(h), 
                handler => this.AssociatedObject.Unloaded += handler, 
                handler => this.AssociatedObject.Unloaded -= handler).Subscribe(@event => this.UnBind());
        }

        private static void CommandBindingChanged(
            DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
        {
            var @this = dependencyObject as ButtonCommand;
            if (@this != null)
            {
                @this.CommandBindingChanged();
            }
        }

        private static void CommandParameterBindingChanged(
            DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
        {
            var @this = dependencyObject as ButtonCommand;
            if (@this != null)
            {
                @this.CommandParameterBindingChanged();
            }
        }

        private void Bind()
        {
            this.subscriptions = new CompositeDisposable
                {
                    Observable.FromEventPattern(
                        (EventHandler<RoutedEventArgs> handler) => new RoutedEventHandler(handler), 
                        handler => this.AssociatedObject.Click += handler, 
                        handler => this.AssociatedObject.Click -= handler).Subscribe(this.OnNextClick)
                };

            this.BindCommand();
            this.BindCommandParameter();
        }

        private void BindCommand()
        {
            Binding binding = this.CommandBinding;
            if (binding != null)
            {
                this.subscriptions.Add(
                    this.AssociatedObject.GetChanges(binding).Subscribe(
                        args =>
                            {
                                this.command = args.NewValue as ICommand;
                                this.CommandChanged();
                            }));
            }
        }

        private void BindCommandParameter()
        {
            Binding binding = this.CommandParameterBinding;
            if (binding != null)
            {
                this.subscriptions.Add(
                    this.AssociatedObject.GetChanges(binding).Subscribe(
                        args =>
                            {
                                this.commandParameter = args.NewValue;
                                this.CommandParameterChanged();
                            }));
            }
        }

        private void CommandBindingChanged()
        {
            if (this.AssociatedObject != null)
            {
                this.BindCommand();
            }
        }

        private void CommandChanged()
        {
            if (this.command != null)
            {
                Observable.FromEventPattern(
                     (EventHandler<EventArgs> handler) => new EventHandler(handler),
                       handler => this.command.CanExecuteChanged += handler,
                       handler => this.command.CanExecuteChanged -= handler).Subscribe(this.OnNextCanExecuteChanged);
                
                this.UpdateCanExecute();
            }
        }

        private void CommandParameterBindingChanged()
        {
            if (this.AssociatedObject != null)
            {
                this.BindCommandParameter();
            }
        }

        private void CommandParameterChanged()
        {
            this.UpdateCanExecute();
        }

        private void OnNextCanExecuteChanged(EventPattern<EventArgs> @event)
        {
            this.UpdateCanExecute();
        }

        private void OnNextClick(EventPattern<RoutedEventArgs> @event)
        {
            if (this.command != null)
            {
                this.command.Execute(this.commandParameter);
            }
        }

        private void UnBind()
        {
            if (this.subscriptions != null)
            {
                this.subscriptions.Dispose();
            }
        }

        private void UpdateCanExecute()
        {
            if (this.command != null)
            {
                this.AssociatedObject.IsEnabled = this.command.CanExecute(this.commandParameter);
            }
        }
    }
}